﻿if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(needle) {
        for (var i = 0; i < this.length; i++) {
            if (this[i] === needle) {
                return i;
            }
        }
        return -1;
    };
}

jAcl =
{
    Acl: function () {
        this.type = 'deny';
        this.resource = '/';
        this.verb = 'read';
        this.subject = '*';
        this.clone = function () {
            var clone = new jAcl.Acl();
            clone.type = this.type;
            clone.resource = this.resource;
            clone.verb = this.verb;
            clone.subject = this.subject;
            return clone;
        }
    },

    allow: function (resource, verb) {
        var subjects = this.__getSubjects(arguments);
        for (var i = 0; i < subjects.length; i++) {
            var acl = new this.Acl();
            acl.subject = subjects[i].toLowerCase();
            acl.resource = resource.toLowerCase();
            acl.verb = verb.toLowerCase();
            acl.type = 'allow';
            this.defaultProvider.setAcl(acl);
        }
    },

    deny: function (resource, verb) {
        var subjects = this.__getSubjects(arguments);
        for (var i = 0; i < subjects.length; i++) {
            var acl = new this.Acl();
            acl.subject = subjects[i].toLowerCase();
            acl.resource = resource.toLowerCase();
            acl.verb = verb.toLowerCase();
            acl.type = 'deny';
            this.defaultProvider.setAcl(acl);
        }
    },

    isAllowed: function (resource, verb) {
        var subjects = this.__getSubjects(arguments);
        var aclss = this.defaultProvider.getAcls(resource, verb).sort(function (x, y) { return x.resource > y.resource });
        var allowed;
        for (var i in aclss) {
            var acl = aclss[i];
            if (!acl.hasOwnProperty("resource"))
                continue;
            if (subjects.indexOf(acl.subject) > -1)
                return acl.type == 'allow';
            if (acl.subject == '*')
                allowed = acl.type == 'allow';
        }
        return allowed != undefined && allowed;
    },

    __getSubjects: function (args) {
        var subjects = new Array(args.length - 2);
        for (var i = 2; i < args.length; i++)
            subjects[i - 2] = args[i].toLowerCase();
        return subjects;
    }
}



jAcl.Provider = function() {
    this.setAcl = function(resource, verb, subject) { };
    this.getAcls = function(resource, verb) { return new Array(); };
    this.setAcls = function() {
        for (var acl in arguments) {
            this.setAcl(acl.resource, acl.verb, acl.subject);
        }
    }
}

jAcl.MemoryProvider = function() {
    this.allowed = {};
    this.denied = {};

    this.prototype = new jAcl.Provider();

    this.setAcl = function(acl) {
        if (acl.resource.charAt(acl.resource.length - 1) != '/')
            acl.resource = acl.resource + '/';
        if (acl.type == 'deny') {
            if (!this.denied.hasOwnProperty(acl.verb))
                this.denied[acl.verb] = {}
            if (!this.denied[acl.verb].hasOwnProperty(acl.resource))
                this.denied[acl.verb][acl.resource] = new Array();
            this.denied[acl.verb][acl.resource].push(acl);
        }
        if (acl.type == 'allow') {
            if (!this.allowed.hasOwnProperty(acl.verb))
                this.allowed[acl.verb] = {}
            if (!this.allowed[acl.verb].hasOwnProperty(acl.resource))
                this.allowed[acl.verb][acl.resource] = new Array();
            this.allowed[acl.verb][acl.resource].push(acl);
        }
    }

    this.getAcls = function(resource, verb) {
        var matchingAcls = new Array();
        if (resource.charAt(resource.length - 1) != '/')
            resource = resource + '/';

        while (resource != '') {
            if (this.denied.hasOwnProperty(verb) && this.denied[verb].hasOwnProperty(resource)) {
                matchingAcls = matchingAcls.concat(this.denied[verb][resource]);
            }
            if (this.allowed.hasOwnProperty(verb) && this.allowed[verb].hasOwnProperty(resource)) {
                matchingAcls = matchingAcls.concat(this.allowed[verb][resource]);
            }
            if (resource.length == 1)
                resource = '';
            resource = resource.replace(/[^\/]+\/+$/, '');
        }
        return matchingAcls;
    }
}

jAcl.defaultProvider = new jAcl.MemoryProvider();

jAcl.RouterProvider = function() {
    var routes = {};
    var _matchingProvider = function(resource) {
        var providers = {};
        for (var registeredResources in routes) {
            if (resource.substring(0, registeredResources.length) == registeredResources) {
                providers[registeredResources] = routes[registeredResources];
            }
        }
        return providers;
    }

    this.prototype = new jAcl.Provider();

    this.setAcl = function(acl) {
        if (acl.resource.charAt(acl.resource.length - 1) != '/')
            acl.resource += '/';
        var providers = _matchingProvider(acl.resource);
        var matchingAcls = new Array();
        for (var registeredResource in providers) {
            for (var i = 0; i < providers[registeredResource].length; i++) {
                var clone = acl.clone();
                clone.resource = acl.resource.substring(registeredResource.length);
                providers[registeredResource][i].setAcl(clone);
            }
        }
    }

    this.getAcls = function(resource, verb) {
        if (resource.charAt(resource.length - 1) != '/')
            resource += '/';
        var providers = _matchingProvider(resource);
        var matchingAcls = new Array();
        for (var registeredResource in providers) {
            for (var i = 0; i < providers[registeredResource].length; i++) {
                var acls = providers[registeredResource][i].getAcls(resource.substring(registeredResource.length), verb);
                for (var aclIndex = 0; aclIndex < acls.length; aclIndex++) {
                    var acl = acls[aclIndex].clone();
                    acl.resource = registeredResource + acl.resource;
                    matchingAcls.push(acl);
                }
            }
        }
        return matchingAcls;
    }


    this.register = function(resource, provider) {
        if (!routes.hasOwnProperty(resource))
            routes[resource] = new Array();
        routes[resource].push(provider);
    }
}